#include "cartridge.h"

#define k4 4096
#define k8 8192
#define k16 16384
#define k32 32768

GB_CART_HEADER header;
GB_CART_MBC mbc;
GB_CART_STATUS status;

long file_length(FILE *file)
{
	long cur = ftell(file);
	
	fseek(file, 0, SEEK_END);
	
	long length = ftell(file);
	
	fseek(file, cur, SEEK_SET);
	
	return length;
}

/* scrive un byte nella ROM */
void gb_cart_write(UINT16 address, UINT8 value)
{
	/* se il gioco è di tipo MBC1 e l'address cade in uno space che va ad influenzare la selezione dei banchi
	   bisogna modificare lo stato di GB_CART_STATUS */
	if (((mbc.flags & MBC_MBC1) == MBC_MBC1) && address <= 0x7FFF)
	{
		/* RAM ENABLE */
		if (address <= 0x1FFF)
		{	
			if (value & 0x0A == 0x0A)
				status.ram_enabled = true;
			else
				status.ram_enabled = false;
		}
		/* seleziona banco ROM, prende i 5 bit più bassi */
		else if (address <= 0x3FFF)
		{
			/* se siamo in ROM banking mode dobbiamo prendere i 2 bit del cur_ram_bank e usarli come 6-7 del rom bank */
			if (!status.ram_banking_mode)
				status.cur_rom_bank = (value & 0x1F) | ((status.cur_ram_bank & 0x03) << 5);
			else
				status.cur_rom_bank = value & 0x1F;
				
			/* se è uno di questi valori dobbiamo incrementare di 1 (specifica del GB) */
			if (status.cur_rom_bank == 0 || status.cur_rom_bank == 20 || status.cur_rom_bank == 40 || status.cur_rom_bank == 60)
				++status.cur_rom_bank;
			
			/* aggiorna il banco switchabile della ROM in base alla richiesta effettuata */	
			status.rom_bank_1 = status.rom + (k16*status.cur_rom_bank);
		}
		/* seleziona banco RAM 0-3, se siamo in rom banking usa i 2 bit per avere più pagine ROM */
		else if (address <= 0x5FFF)
		{
			if (status.ram_banking_mode)
			{
				status.cur_ram_bank = value & 0x03;
				
				/* aggiorna il banco di ram (4 x 8kb pagine) */
				status.ram_bank = status.ram + (k8*status.cur_ram_bank);
			}
		}
		/* seleziona ROM banking o RAM banking */
		else if (address <= 0x7FFF)
		{
			if (value & 0x01 == 0x00)
				status.ram_banking_mode = false;
			else if (value & 0x01 == 0x01)
				status.ram_banking_mode = true;
				
			/* TODO: aggiorna rom_bank_1 o ram_bank di conseguenza */
		}		
	}
	/* RAM nella cart (più banchi switchabili nel caso, da 8kb l'uno) */
	else if (address >= 0xA000 && address <= 0xBFFF)
	{
		UINT16 offset = address - 0xA000;
		
		/* scrive basandosi sull'offset nel banco di RAM selezionato */
		status.ram_bank[offset] = value;
		
	}
}

/* legge un byte dalla ROM */
UINT8 gb_cart_read(UINT16 address)
{
	/* il tipo è solo ROM: max 32Kb non switchabili */
	if (mbc.flags & MBC_ROM == MBC_ROM)
	{
		/* primi 16kb -> rom_bank_0 */
		if (address <= 0x3FFF)
			return status.rom_bank_0[address];
		/* secondi 16k -> rom_bank_1 */
		else if (address >= 0x4000 && address <= 0x7FFF)
			return status.rom_bank_1[address];
	}
	if (mbc.flags & MBC_RAM == MBC_RAM)
	{
		
	}

	return 0;
}

int gb_cart_rom_size()
{
	if (header.rom_size <= (UINT8)0x07)
		return 0x8000 << header.rom_size;
	else
	{
		/* TODO 0x52, 0x53, 0x54 */
	}
	
	return 0;
}

int gb_cart_ram_size()
{
	switch (header.ram_size)
	{
		case 0x01: return 2048;
		case 0x02: return k8;
		case 0x03: return k32;
		default:
		case 0x00: return -1;
	}
}

bool gb_cart_is_cgb(void)
{
	return header.cgb_flag;
}

bool gb_cart_is_sgb(void)
{
	return header.sgb_flag;
}

void gb_cart_init(void)
{
	/* TODO: resetta lo status, sonasega */
}

void gb_cart_load(char *rom_name)
{
	FILE *in = fopen(rom_name, "rb");
	long length = file_length(in);
	
	fseek(in, 0x100, SEEK_SET);
	fread(&header, sizeof(GB_CART_HEADER), 1, in);
	
	mbc.flags = 0x00;
	
	/* in base al cart_type assegna le flag della rom */
	if (header.cart_type == 0x00 || header.cart_type == 0x08 || header.cart_type == 0x09)
		mbc.flags |= MBC_ROM;
	
	if (header.cart_type >= 0x01 && header.cart_type <= 0x03)
		mbc.flags |= MBC_MBC1;
		
	if (header.cart_type == 0x05 || header.cart_type == 0x06)
		mbc.flags |= MBC_MBC2;
		
	if (header.cart_type >= 0x0F && header.cart_type <= 0x13)
		mbc.flags |= MBC_MBC3;
		
	if (header.cart_type == 0x02 || header.cart_type == 0x03 || header.cart_type == 0x08 || header.cart_type == 0x09 ||
	    header.cart_type == 0x10 || header.cart_type == 0x12 || header.cart_type == 0x13)
		mbc.flags |= MBC_RAM;
	
	if (header.cart_type == 0x03 || header.cart_type == 0x06 || header.cart_type == 0x09 || header.cart_type == 0x0F ||
	    header.cart_type == 0x10 || header.cart_type == 0x13)
		mbc.flags |= MBC_BATTERY;
		
	if (header.cart_type == 0x0F || header.cart_type == 0x10)
		mbc.flags |= MBC_TIMER;
	
	char *tmp_name = new char[12];
	memcpy(tmp_name, header.title, 11);
	
	tmp_name[11] = 0x00;
	
#ifdef DEBUG
	
	printf("Rom name: %s\r\n", tmp_name);
	printf("Effective file length: %u\r\n", length);
	printf("ROM size: %u\r\n", gb_cart_rom_size());
	printf("RAM size: %u\r\n", gb_cart_ram_size());
	printf("Destination: %s\r\n", (header.dest_code == 0x00)?("Japanese"):("Not Japanese"));
	printf("Header CRC: %d\r\n", header.checksum);
	printf("Cart props %d\r\n", mbc.flags);
	printf("-------------\r\n\r\n");
	
#endif

	if ((mbc.flags & MBC_ROM) == MBC_ROM)
	{
		printf("ROM allocating %d bytes\r\n", length);
		
		/* il tipo è ROM -> dimensione max 32kb in due blocchi da 16kb */
		if (length > k32)
			printf("ROM format invalid!\r\n");

		status.rom_bank_0 = (UINT8*)malloc(k16);
		
		fread(status.rom_bank_0, 1, k16, in);
		
		status.rom_bank_1 = (UINT8*)malloc(length - k16);
		
		fread(status.rom_bank_1, 1, length - k16, in);
	}
	
	if  ((mbc.flags & MBC_RAM) == MBC_RAM)
	{
		if ((mbc.flags & MBC_ROM) == MBC_ROM)
		{
			printf("RAM allocating %d bytes\r\n", gb_cart_ram_size());
			
			status.ram_enabled = false;
			
			status.ram_bank = (UINT8*)malloc(gb_cart_ram_size());
			status.ram = status.ram_bank;
		}
		else if ((mbc.flags & MBC_MBC1) == MBC_MBC1)
		{
#ifdef DEBUG
			printf("RAM allocating %d bytes\r\n", gb_cart_ram_size());
#endif DEBUG				
			status.ram_enabled = false;
			status.cur_ram_bank = 0;
			
			status.ram_bank = (UINT8*)malloc(gb_cart_ram_size());
			status.ram = status.ram_bank;
		}
	}
	
	if ((mbc.flags & MBC_MBC1) == MBC_MBC1)
	{
		int n = length / k16;
		
		/* allochiamo n banks da 16kb */
		printf("MBC1 allocating %d x 16kb = %d bytes\r\n", n, length);
		
		status.rom_bank_0 = (UINT8*)malloc(k16);
		
		status.rom = (UINT8*)malloc(header.rom_size);
		
		status.rom_bank_1 = status.rom;
		status.cur_rom_bank = 0;
	}
		
		
	
	fclose(in);
}